// ========================================================================
// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
// You may elect to redistribute this code under either of these licenses.
// ========================================================================

package org.eclipse.jetty.server.ssl;

import java.io.IOException;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;

import org.eclipse.jetty.http.HttpParser;
import org.eclipse.jetty.http.HttpSchemes;
import org.eclipse.jetty.http.ssl.SslContextFactory;
import org.eclipse.jetty.io.Buffer;
import org.eclipse.jetty.io.Buffers;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.ThreadLocalBuffers;
import org.eclipse.jetty.io.bio.SocketEndPoint;
import org.eclipse.jetty.io.nio.DirectNIOBuffer;
import org.eclipse.jetty.io.nio.IndirectNIOBuffer;
import org.eclipse.jetty.io.nio.SelectChannelEndPoint;
import org.eclipse.jetty.io.nio.SelectorManager.SelectSet;
import org.eclipse.jetty.io.nio.SslSelectChannelEndPoint;
import org.eclipse.jetty.server.HttpConnection;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.nio.SelectChannelConnector;
import org.eclipse.jetty.util.log.Log;

/* ------------------------------------------------------------ */
/**
 * SslSelectChannelConnector.
 *
 * @org.apache.xbean.XBean element="sslConnector" description="Creates an NIO ssl connector"
 */
public class SslSelectChannelConnector extends SelectChannelConnector implements SslConnector
{
    private final SslContextFactory _sslContextFactory;
    private Buffers _sslBuffers;

    /* ------------------------------------------------------------ */
    public SslSelectChannelConnector()
    {
        this(new SslContextFactory(SslContextFactory.DEFAULT_KEYSTORE_PATH));
    }

    /* ------------------------------------------------------------ */
    public SslSelectChannelConnector(SslContextFactory sslContextFactory)
    {
        _sslContextFactory = sslContextFactory;
        setUseDirectBuffers(false);
    }

    /* ------------------------------------------------------------ */
    /**
     * Allow the Listener a chance to customise the request. before the server
     * does its stuff. <br>
     * This allows the required attributes to be set for SSL requests. <br>
     * The requirements of the Servlet specs are:
     * <ul>
     * <li> an attribute named "javax.servlet.request.ssl_session_id" of type
     * String (since Servlet Spec 3.0).</li>
     * <li> an attribute named "javax.servlet.request.cipher_suite" of type
     * String.</li>
     * <li> an attribute named "javax.servlet.request.key_size" of type Integer.</li>
     * <li> an attribute named "javax.servlet.request.X509Certificate" of type
     * java.security.cert.X509Certificate[]. This is an array of objects of type
     * X509Certificate, the order of this array is defined as being in ascending
     * order of trust. The first certificate in the chain is the one set by the
     * client, the next is the one used to authenticate the first, and so on.
     * </li>
     * </ul>
     *
     * @param endpoint
     *                The Socket the request arrived on. This should be a
     *                {@link SocketEndPoint} wrapping a {@link SSLSocket}.
     * @param request
     *                HttpRequest to be customised.
     */
    @Override
    public void customize(EndPoint endpoint, Request request) throws IOException
    {
        request.setScheme(HttpSchemes.HTTPS);
        super.customize(endpoint,request);

        SslSelectChannelEndPoint sslHttpChannelEndpoint=(SslSelectChannelEndPoint)endpoint;
        SSLEngine sslEngine=sslHttpChannelEndpoint.getSSLEngine();
        SSLSession sslSession=sslEngine.getSession();

        SslCertificates.customize(sslSession,endpoint,request);
    }

    /* ------------------------------------------------------------ */
    /**
     * @return True if SSL re-negotiation is allowed (default false)
     * @deprecated
     */
    @Deprecated
    public boolean isAllowRenegotiate()
    {
        return _sslContextFactory.isAllowRenegotiate();
    }

    /* ------------------------------------------------------------ */
    /**
     * Set if SSL re-negotiation is allowed. CVE-2009-3555 discovered
     * a vulnerability in SSL/TLS with re-negotiation.  If your JVM
     * does not have CVE-2009-3555 fixed, then re-negotiation should
     * not be allowed.
     * @param allowRenegotiate true if re-negotiation is allowed (default false)
     * @deprecated
     */
    @Deprecated
    public void setAllowRenegotiate(boolean allowRenegotiate)
    {
        _sslContextFactory.setAllowRenegotiate(allowRenegotiate);
    }

    /* ------------------------------------------------------------ */
    /**
     * @see org.eclipse.jetty.server.ssl.SslConnector#getExcludeCipherSuites()
     * @deprecated
     */
    @Deprecated
    public String[] getExcludeCipherSuites()
    {
        return _sslContextFactory.getExcludeCipherSuites();
    }

    /* ------------------------------------------------------------ */
    /**
     * @see org.eclipse.jetty.server.ssl.SslConnector#setExcludeCipherSuites(java.lang.String[])
     * @deprecated
     */
    @Deprecated
    public void setExcludeCipherSuites(String[] cipherSuites)
    {
        _sslContextFactory.setExcludeCipherSuites(cipherSuites);
    }

    /* ------------------------------------------------------------ */
    /**
     * @see org.eclipse.jetty.server.ssl.SslConnector#getExcludeCipherSuites()
     * @deprecated
     */
    @Deprecated
    public String[] getIncludeCipherSuites()
    {
        return _sslContextFactory.getIncludeCipherSuites();
    }

    /* ------------------------------------------------------------ */
    /**
     * @see org.eclipse.jetty.server.ssl.SslConnector#setExcludeCipherSuites(java.lang.String[])
     * @deprecated
     */
    @Deprecated
    public void setIncludeCipherSuites(String[] cipherSuites)
    {
        _sslContextFactory.setIncludeCipherSuites(cipherSuites);
    }

    /* ------------------------------------------------------------ */
    /**
     * @see org.eclipse.jetty.server.ssl.SslConnector#setPassword(java.lang.String)
     * @deprecated
     */
    @Deprecated
    public void setPassword(String password)
    {
        _sslContextFactory.setKeyStorePassword(password);
    }

    /* ------------------------------------------------------------ */
    /**
     * @see org.eclipse.jetty.server.ssl.SslConnector#setTrustPassword(java.lang.String)
     * @deprecated
     */
    @Deprecated
    public void setTrustPassword(String password)
    {
        _sslContextFactory.setTrustStorePassword(password);
    }

    /* ------------------------------------------------------------ */
    /**
     * @see org.eclipse.jetty.server.ssl.SslConnector#setKeyPassword(java.lang.String)
     * @deprecated
     */
    @Deprecated
    public void setKeyPassword(String password)
    {
        _sslContextFactory.setKeyManagerPassword(password);
    }

    /* ------------------------------------------------------------ */
    /**
     * Unsupported.
     * 
     * TODO: we should remove this as it is no longer an overridden method from SslConnector (like it was in the past)
     * @deprecated
     */
    @Deprecated
    public String getAlgorithm()
    {
        throw new UnsupportedOperationException();
    }

    /* ------------------------------------------------------------ */
    /**
     * Unsupported.
     * 
     * TODO: we should remove this as it is no longer an overridden method from SslConnector (like it was in the past)
     * @deprecated
     */
    @Deprecated
    public void setAlgorithm(String algorithm)
    {
        throw new UnsupportedOperationException();
    }

    /* ------------------------------------------------------------ */
    /**
     * @see org.eclipse.jetty.server.ssl.SslConnector#getProtocol()
     * @deprecated
     */
    @Deprecated
    public String getProtocol()
    {
        return _sslContextFactory.getProtocol();
    }

    /* ------------------------------------------------------------ */
    /**
     * @see org.eclipse.jetty.server.ssl.SslConnector#setProtocol(java.lang.String)
     * @deprecated
     */
    @Deprecated
    public void setProtocol(String protocol)
    {
        _sslContextFactory.setProtocol(protocol);
    }

    /* ------------------------------------------------------------ */
    /**
     * @see org.eclipse.jetty.server.ssl.SslConnector#setKeystore(java.lang.String)
     * @deprecated
     */
    @Deprecated
    public void setKeystore(String keystore)
    {
        _sslContextFactory.setKeyStore(keystore);
    }

    /* ------------------------------------------------------------ */
    /**
     * @see org.eclipse.jetty.server.ssl.SslConnector#getKeystore()
     * @deprecated
     */
    @Deprecated
    public String getKeystore()
    {
        return _sslContextFactory.getKeyStore();
    }

    /* ------------------------------------------------------------ */
    /**
     * @see org.eclipse.jetty.server.ssl.SslConnector#getKeystoreType()
     * @deprecated
     */
    @Deprecated
    public String getKeystoreType()
    {
        return _sslContextFactory.getKeyStoreType();
    }

    /* ------------------------------------------------------------ */
    /**
     * @see org.eclipse.jetty.server.ssl.SslConnector#getNeedClientAuth()
     * @deprecated
     */
    @Deprecated
    public boolean getNeedClientAuth()
    {
        return _sslContextFactory.getNeedClientAuth();
    }

    /* ------------------------------------------------------------ */
    /**
     * @see org.eclipse.jetty.server.ssl.SslConnector#getWantClientAuth()
     * @deprecated
     */
    @Deprecated
    public boolean getWantClientAuth()
    {
        return _sslContextFactory.getWantClientAuth();
    }

    /* ------------------------------------------------------------ */
    /**
     * @see org.eclipse.jetty.server.ssl.SslConnector#setNeedClientAuth(boolean)
     * @deprecated
     */
    @Deprecated
    public void setNeedClientAuth(boolean needClientAuth)
    {
        _sslContextFactory.setNeedClientAuth(needClientAuth);
    }

    /* ------------------------------------------------------------ */
    /**
     * @see org.eclipse.jetty.server.ssl.SslConnector#setWantClientAuth(boolean)
     * @deprecated
     */
    @Deprecated
    public void setWantClientAuth(boolean wantClientAuth)
    {
        _sslContextFactory.setWantClientAuth(wantClientAuth);
    }

    /* ------------------------------------------------------------ */
    /**
     * @see org.eclipse.jetty.server.ssl.SslConnector#setKeystoreType(java.lang.String)
     * @deprecated
     */
    @Deprecated
    public void setKeystoreType(String keystoreType)
    {
        _sslContextFactory.setKeyStoreType(keystoreType);
    }

    /* ------------------------------------------------------------ */
    /**
     * @see org.eclipse.jetty.server.ssl.SslConnector#getProvider()
     * @deprecated
     */
    @Deprecated
    public String getProvider()
    {
        return _sslContextFactory.getProvider();
    }

    /* ------------------------------------------------------------ */
    /**
     * @see org.eclipse.jetty.server.ssl.SslConnector#getSecureRandomAlgorithm()
     * @deprecated
     */
    @Deprecated
    public String getSecureRandomAlgorithm()
    {
        return _sslContextFactory.getSecureRandomAlgorithm();
    }

    /* ------------------------------------------------------------ */
    /**
     * @see org.eclipse.jetty.server.ssl.SslConnector#getSslKeyManagerFactoryAlgorithm()
     * @deprecated
     */
    @Deprecated
    public String getSslKeyManagerFactoryAlgorithm()
    {
        return _sslContextFactory.getSslKeyManagerFactoryAlgorithm();
    }

    /* ------------------------------------------------------------ */
    /**
     * @see org.eclipse.jetty.server.ssl.SslConnector#getSslTrustManagerFactoryAlgorithm()
     * @deprecated
     */
    @Deprecated
    public String getSslTrustManagerFactoryAlgorithm()
    {
        return _sslContextFactory.getTrustManagerFactoryAlgorithm();
    }

    /* ------------------------------------------------------------ */
    /**
     * @see org.eclipse.jetty.server.ssl.SslConnector#getTruststore()
     * @deprecated
     */
    @Deprecated
    public String getTruststore()
    {
        return _sslContextFactory.getTrustStore();
    }

    /* ------------------------------------------------------------ */
    /**
     * @see org.eclipse.jetty.server.ssl.SslConnector#getTruststoreType()
     * @deprecated
     */
    @Deprecated
    public String getTruststoreType()
    {
        return _sslContextFactory.getTrustStoreType();
    }

    /* ------------------------------------------------------------ */
    /**
     * @see org.eclipse.jetty.server.ssl.SslConnector#setProvider(java.lang.String)
     * @deprecated
     */
    @Deprecated
    public void setProvider(String provider)
    {
        _sslContextFactory.setProvider(provider);
    }

    /* ------------------------------------------------------------ */
    /**
     * @see org.eclipse.jetty.server.ssl.SslConnector#setSecureRandomAlgorithm(java.lang.String)
     * @deprecated
     */
    @Deprecated
    public void setSecureRandomAlgorithm(String algorithm)
    {
        _sslContextFactory.setSecureRandomAlgorithm(algorithm);
    }

    /* ------------------------------------------------------------ */
    /**
     * @see org.eclipse.jetty.server.ssl.SslConnector#setSslKeyManagerFactoryAlgorithm(java.lang.String)
     * @deprecated
     */
    @Deprecated
    public void setSslKeyManagerFactoryAlgorithm(String algorithm)
    {
        _sslContextFactory.setSslKeyManagerFactoryAlgorithm(algorithm);
    }

    /* ------------------------------------------------------------ */
    /**
     * @see org.eclipse.jetty.server.ssl.SslConnector#setSslTrustManagerFactoryAlgorithm(java.lang.String)
     * @deprecated
     */
    @Deprecated
    public void setSslTrustManagerFactoryAlgorithm(String algorithm)
    {
        _sslContextFactory.setTrustManagerFactoryAlgorithm(algorithm);
    }

    /* ------------------------------------------------------------ */
    /**
     * @see org.eclipse.jetty.server.ssl.SslConnector#setTruststore(java.lang.String)
     * @deprecated
     */
    @Deprecated
    public void setTruststore(String truststore)
    {
        _sslContextFactory.setTrustStore(truststore);
    }

    /* ------------------------------------------------------------ */
    /**
     * @see org.eclipse.jetty.server.ssl.SslConnector#setTruststoreType(java.lang.String)
     * @deprecated
     */
    @Deprecated
    public void setTruststoreType(String truststoreType)
    {
        _sslContextFactory.setTrustStoreType(truststoreType);
    }

    /* ------------------------------------------------------------ */
    /**
     * @see org.eclipse.jetty.server.ssl.SslConnector#setSslContext(javax.net.ssl.SSLContext)
     * @deprecated
     */
    @Deprecated
    public void setSslContext(SSLContext sslContext)
    {
        _sslContextFactory.setSslContext(sslContext);
    }

    /* ------------------------------------------------------------ */
    /**
     * @see org.eclipse.jetty.server.ssl.SslConnector#setSslContext(javax.net.ssl.SSLContext)
     * @deprecated
     */
    @Deprecated
    public SSLContext getSslContext()
    {
        return _sslContextFactory.getSslContext();
    }

    /* ------------------------------------------------------------ */
    /**
     * @see org.eclipse.jetty.server.ssl.SslConnector#getSslContextFactory()
     */
//    @Override
    public SslContextFactory getSslContextFactory()
    {
        return _sslContextFactory;
    }

    /* ------------------------------------------------------------ */
    /**
     * By default, we're confidential, given we speak SSL. But, if we've been
     * told about an confidential port, and said port is not our port, then
     * we're not. This allows separation of listeners providing INTEGRAL versus
     * CONFIDENTIAL constraints, such as one SSL listener configured to require
     * client certs providing CONFIDENTIAL, whereas another SSL listener not
     * requiring client certs providing mere INTEGRAL constraints.
     */
    @Override
    public boolean isConfidential(Request request)
    {
        final int confidentialPort=getConfidentialPort();
        return confidentialPort==0||confidentialPort==request.getServerPort();
    }

    /* ------------------------------------------------------------ */
    /**
     * By default, we're integral, given we speak SSL. But, if we've been told
     * about an integral port, and said port is not our port, then we're not.
     * This allows separation of listeners providing INTEGRAL versus
     * CONFIDENTIAL constraints, such as one SSL listener configured to require
     * client certs providing CONFIDENTIAL, whereas another SSL listener not
     * requiring client certs providing mere INTEGRAL constraints.
     */
    @Override
    public boolean isIntegral(Request request)
    {
        final int integralPort=getIntegralPort();
        return integralPort==0||integralPort==request.getServerPort();
    }

    /* ------------------------------------------------------------------------------- */
    @Override
    protected SelectChannelEndPoint newEndPoint(SocketChannel channel, SelectSet selectSet, SelectionKey key) throws IOException
    {
        SslSelectChannelEndPoint endp = new SslSelectChannelEndPoint(_sslBuffers,channel,selectSet,key,createSSLEngine(), SslSelectChannelConnector.this._maxIdleTime);
        endp.setAllowRenegotiate(_sslContextFactory.isAllowRenegotiate());
        return endp;
    }

    /* ------------------------------------------------------------------------------- */
    @Override
    protected Connection newConnection(SocketChannel channel, SelectChannelEndPoint endpoint)
    {
        HttpConnection connection=(HttpConnection)super.newConnection(channel,endpoint);
        ((HttpParser)connection.getParser()).setForceContentBuffer(true);
        return connection;
    }

    /* ------------------------------------------------------------ */
    protected SSLEngine createSSLEngine() throws IOException
    {
        SSLEngine engine = null;
        try
        {
            engine = _sslContextFactory.getSslContext().createSSLEngine();
            engine.setUseClientMode(false);

            if (_sslContextFactory.getWantClientAuth())
                engine.setWantClientAuth(_sslContextFactory.getWantClientAuth());
            if (_sslContextFactory.getNeedClientAuth())
                engine.setNeedClientAuth(_sslContextFactory.getNeedClientAuth());

            engine.setEnabledCipherSuites(
                    _sslContextFactory.selectCipherSuites(engine.getEnabledCipherSuites(),
                                                          engine.getSupportedCipherSuites()));
        }
        catch (Exception e)
        {
            Log.warn("Error creating sslEngine -- closing this connector",e);
            close();
            throw new IllegalStateException(e);
        }
        return engine;
    }

    /* ------------------------------------------------------------ */
    /**
     * @see org.eclipse.jetty.server.nio.SelectChannelConnector#doStart()
     */
    @Override
    protected void doStart() throws Exception
    {
        if (!_sslContextFactory.checkConfig())
        {
            throw new IllegalStateException("SSL context is not configured correctly.");
        }
        
        _sslContextFactory.start(); 
        
        SSLEngine sslEngine = _sslContextFactory.getSslContext().createSSLEngine();

        sslEngine.setUseClientMode(false);
        sslEngine.setWantClientAuth(_sslContextFactory.getWantClientAuth());
        sslEngine.setNeedClientAuth(_sslContextFactory.getNeedClientAuth());
        
        sslEngine.setEnabledCipherSuites(_sslContextFactory.selectCipherSuites(
                                            sslEngine.getEnabledCipherSuites(),
                                            sslEngine.getSupportedCipherSuites()));
        
        SSLSession sslSession = sslEngine.getSession();

        ThreadLocalBuffers buffers = new ThreadLocalBuffers()
        {
            @Override
            protected Buffer newBuffer(int size)
            {
                if (getUseDirectBuffers())
                    return new DirectNIOBuffer(size);
                return new IndirectNIOBuffer(size);
            }
            @Override
            protected Buffer newHeader(int size)
            {
                if (getUseDirectBuffers())
                    return new DirectNIOBuffer(size);
                return new IndirectNIOBuffer(size);
            }
            @Override
            protected boolean isHeader(Buffer buffer)
            {
                return true;
            }
        };
        buffers.setBufferSize(sslSession.getApplicationBufferSize());
        buffers.setHeaderSize(sslSession.getApplicationBufferSize());
        _sslBuffers=buffers;

        if (getRequestHeaderSize()<sslSession.getApplicationBufferSize())
            setRequestHeaderSize(sslSession.getApplicationBufferSize());
        if (getRequestBufferSize()<sslSession.getApplicationBufferSize())
            setRequestBufferSize(sslSession.getApplicationBufferSize());

        super.doStart();
    }
    
    /* ------------------------------------------------------------ */
    /**
     * @see org.eclipse.jetty.server.nio.SelectChannelConnector#doStop()
     */
    @Override
    protected void doStop() throws Exception
    {
        _sslContextFactory.stop(); 

        super.doStop();
    }

    /* ------------------------------------------------------------ */
    /**
     * @return SSL buffers
     */
    public Buffers getSslBuffers()
    {
        return _sslBuffers;
    }
}
